﻿
using System;
using System.Data.SqlClient;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;

public class Class1
{
    public interface ITest
    {
        string P1 { get; }
        int P2 { get; }
    }
    
}

public class Util
{
    public static T GetValue<T>(SqlDataReader reader, string propertyName)
    {
        object q = "qqqqqqqqq";
        return (T) q;
    }

    public static string GetString(SqlDataReader reader, string fieldName)
    {
        return "ccccccccccccccccc";
    }

    public static int GetInt(SqlDataReader reader, string fieldName)
    {
        return 77;
        return reader.GetInt32(reader.GetOrdinal(fieldName));
    }

    public static int GetInt2(SqlDataReader reader, int fieldIndex)
    {
        return 77;
    }
}

class PropertyBuilderDemo
{
    public static Func<SqlDataReader, T> BuildDynamicTypeWithProperties<T>(Func<Type, MethodInfo> func)
    {
        var assemblyName = new AssemblyName {Name = Guid.NewGuid().ToString()};

        var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);

        var typeBuilder = moduleBuilder.DefineType(
            typeof (T).Name + "_" + Guid.NewGuid(), TypeAttributes.NotPublic, null, new[] {typeof (T)});

        foreach (var property in typeof (T).GetProperties())
        {
            CreateProperty(typeBuilder, property);            
        }

        var type = typeBuilder.CreateType();

        var dynamicMethod = new DynamicMethod(Guid.NewGuid().ToString(),
                                      type,
                                      new[] {typeof (SqlDataReader)},
                                      moduleBuilder);

        var ilGenerator = dynamicMethod.GetILGenerator();
        ilGenerator.DeclareLocal(type);
        ilGenerator.Emit(OpCodes.Newobj, type.GetConstructor(new Type[] { }));
        ilGenerator.Emit(OpCodes.Stloc_0);
        foreach (var property in type.GetProperties())
        {
            ilGenerator.Emit(OpCodes.Ldloc_0);
            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldstr, property.Name);
            ilGenerator.EmitCall(OpCodes.Call, func(property.PropertyType), null);
            ilGenerator.EmitCall(OpCodes.Callvirt, property.GetSetMethod(), null);
        }
        ilGenerator.Emit(OpCodes.Ldloc_0);
        ilGenerator.Emit(OpCodes.Ret);

        dynamicMethod.DefineParameter(1, ParameterAttributes.In, "arg1");

        return (Func<SqlDataReader, T>)dynamicMethod.CreateDelegate(typeof(Func<SqlDataReader, T>));
    }

    private static MethodInfo MethodInfo(Type type)
    {
        MethodInfo methodInfo;
        if (type == typeof (string))
        {
            methodInfo = typeof (Util).GetMethod("GetString");
        }
        else if (type == typeof (int))
        {
            methodInfo = typeof (Util).GetMethod("GetInt");
        }
        else
        {
            throw new NotImplementedException();
        }
        return methodInfo;
    }

    private static void qwe2(TypeBuilder typeBuilder)
    {
        Type[] helloArgs = {typeof (SqlDataReader), typeof (string)};
        var getterMethodBuilder = typeBuilder.DefineMethod("Test",
                                                           MethodAttributes.Public,
                                                           null,
                                                           helloArgs);

        var getterIlGenerator = getterMethodBuilder.GetILGenerator();


        // Create an array that specifies the types of the parameters
        // of the dynamic method. This dynamic method has a String
        // parameter and an Integer parameter.
        //Type[] helloArgs = { typeof(SqlDataReader), typeof(string) };

        // Create a dynamic method with the name "Hello", a return type
        // of Integer, and two parameters whose types are specified by
        // the array helloArgs. Create the method in the module that
        // defines the String class.
        //DynamicMethod hello = new DynamicMethod("Hello",
        //    typeof(int),
        //    helloArgs,
        //    typeof(string).Module);

        MethodInfo methodInfo = typeof(Util).GetMethod("GetValue").MakeGenericMethod(typeof(string));

        // Get an ILGenerator and emit a body for the dynamic method,
        // using a stream size larger than the IL that will be
        // emitted.
        ILGenerator il = getterIlGenerator;
        il.DeclareLocal(typeof (Test));
        //il.Emit(OpCodes.Nop);
        il.Emit(OpCodes.Newobj, typeof(Test).GetConstructor(new Type[] {}));
        il.Emit(OpCodes.Stloc_0);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldarg_2);
        //// Call the overload of Console.WriteLine that prints a string.
        il.EmitCall(OpCodes.Call, methodInfo, null);
        il.EmitCall(OpCodes.Callvirt, typeof(Test).GetProperty("P1").GetSetMethod(), null);
        //// The Hello method returns the value of the second argument;
        //// to do this, load the onto the stack and return.
        ////il.Emit(OpCodes.Ldarg_1);
        //il.Emit(OpCodes.Nop);
        il.Emit(OpCodes.Ret);

        // Add parameter information to the dynamic method. (This is not
        // necessary, but can be useful for debugging.) For each parameter,
        // identified by position, supply the parameter attributes and a 
        // parameter name.
        //hello.DefineParameter(1, ParameterAttributes.In, "message");
        //hello.DefineParameter(2, ParameterAttributes.In, "valueToReturn");

        // Create a delegate that represents the dynamic method. This
        // action completes the method. Any further attempts to
        //// change the method are ignored.
        ////HelloDelegate hi =
        ////    (HelloDelegate)hello.CreateDelegate(typeof(HelloDelegate));
    }

    private static void CreateProperty(TypeBuilder typeBuilder, PropertyInfo propertyInfo)
    {
        var fieldBuilder = typeBuilder.DefineField(
            GetPascalCase(propertyInfo.Name), propertyInfo.PropertyType, FieldAttributes.Private);

        var propertyBuilder = typeBuilder.DefineProperty(
            propertyInfo.Name, PropertyAttributes.None, propertyInfo.PropertyType, null);

        const MethodAttributes getSetMethodAttributes =
            MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig
            | MethodAttributes.SpecialName;

        var getterMethodBuilder = typeBuilder.DefineMethod("get_" + propertyInfo.Name,
                                                           getSetMethodAttributes,
                                                           propertyInfo.PropertyType,
                                                           Type.EmptyTypes);

        var getterIlGenerator = getterMethodBuilder.GetILGenerator();
        getterIlGenerator.Emit(OpCodes.Ldarg_0);
        getterIlGenerator.Emit(OpCodes.Ldfld, fieldBuilder);
        getterIlGenerator.Emit(OpCodes.Ret);

        var setterMethodBuilder = typeBuilder.DefineMethod("set_" + propertyInfo.Name,
                                                          getSetMethodAttributes,
                                                          null,
                                                          new[] { propertyInfo.PropertyType });

        var setterIlGenerator = setterMethodBuilder.GetILGenerator();
        setterIlGenerator.Emit(OpCodes.Ldarg_0);
        setterIlGenerator.Emit(OpCodes.Ldarg_1);
        setterIlGenerator.Emit(OpCodes.Stfld, fieldBuilder);
        setterIlGenerator.Emit(OpCodes.Ret);

        propertyBuilder.SetGetMethod(getterMethodBuilder);
        propertyBuilder.SetSetMethod(setterMethodBuilder);
    }

    private static string GetPascalCase(string name)
    {
        return name.Substring(0, 1).ToLower() + name.Substring(1);
    }

    public static void Main()
    {
        var custDataType = BuildDynamicTypeWithProperties<Class1.ITest>(MethodInfo)(null);

        //SqlDataReader sqlDataReader = null;
        //qwe(sqlDataReader, "qwwwwww");

        //PropertyInfo[] custDataPropInfo = custDataType.GetProperties();
        //foreach (PropertyInfo pInfo in custDataPropInfo)
        //{
        //    Console.WriteLine("Property '{0}' created!", pInfo.ToString());
        //}

        //Console.WriteLine("---");
        //// Note that when invoking a property, you need to use the proper BindingFlags -
        //// BindingFlags.SetProperty when you invoke the "set" behavior, and 
        //// BindingFlags.GetProperty when you invoke the "get" behavior. Also note that
        //// we invoke them based on the name we gave the property, as expected, and not
        //// the name of the methods we bound to the specific property behaviors.

        //object custData = Activator.CreateInstance(custDataType);
        //custDataType.InvokeMember("CustomerName", BindingFlags.SetProperty,
        //                              null, custData, new object[] { "Joe User" });

        //Console.WriteLine("The customerName field of instance custData has been set to '{0}'.",
        //                   custDataType.InvokeMember("CustomerName", BindingFlags.GetProperty,
        //                                              null, custData, new object[] { }));
    }

    public Test qwe(SqlDataReader sqlDataReader)
    {
        return new Test
                   {
                       P1 = Util.GetString(sqlDataReader, "qweqqqqqqqqqqqqqqqq"),
                       P2 = Util.GetInt2(sqlDataReader, 33333)
                   };
    }

    public Test2 qwe2(SqlDataReader sqlDataReader)
    {
        return new Test2(
            Util.GetString(sqlDataReader, "qweqqqqqqqqqqqqqqqq"),
            Util.GetInt2(sqlDataReader, 33333));
    }


}

public class Test2
{
    public Test2(string P1, int P2)
    {
    }
}

public class Test: Class1.ITest
{
    private string p1;
    public string P1
    {
        get { return p1; }
        set { p1 = value; }
    }

    public int P2
    {
        get { throw new NotImplementedException(); }
        set { }
    }
}

// --- O U T P U T ---
// The output should be as follows:
// -------------------
// Property 'System.String CustomerName [System.String]' created!
// ---
// The customerName field of instance custData has been set to 'Joe User'.
// -------------------